package com.simpou.commons.utils.string;

import com.simpou.commons.utils.functional.AbstractActionThrowErrors;
import com.simpou.commons.utils.functional.Action;
import com.simpou.commons.utils.functional.DummyAction;
import com.simpou.commons.utils.lang.Conditionals;
import com.simpou.commons.utils.reflection.Casts;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;


/**
 * Operações de conversões texto.
 *
 * @author Jonas Pereira
 * @since 2011-06-25
 */
public final class StringConversorHelper {

    public static final Map<Class<?>, Action<String, ?>> STRING_CONVERSORS;

    static {
        final Map<Class<?>, Action<String, ?>> mapConversors = new HashMap<Class<?>, Action<String, ?>>();

        mapConversors.put(Boolean.class, StringConversors.BOOLEAN);
        mapConversors.put(Character.class, StringConversors.CHARACTER);
        mapConversors.put(Byte.class, StringConversors.BYTE);
        mapConversors.put(Short.class, StringConversors.SHORT);
        mapConversors.put(Integer.class, StringConversors.INTEGER);
        mapConversors.put(Long.class, StringConversors.LONG);
        mapConversors.put(Float.class, StringConversors.FLOAT);
        mapConversors.put(Double.class, StringConversors.DOUBLE);

        mapConversors.put(boolean.class, StringConversors.BOOLEAN);
        mapConversors.put(char.class, StringConversors.CHARACTER);
        mapConversors.put(byte.class, StringConversors.BYTE);
        mapConversors.put(short.class, StringConversors.SHORT);
        mapConversors.put(int.class, StringConversors.INTEGER);
        mapConversors.put(long.class, StringConversors.LONG);
        mapConversors.put(float.class, StringConversors.FLOAT);
        mapConversors.put(double.class, StringConversors.DOUBLE);

        mapConversors.put(String.class, StringConversors.STRING);
        mapConversors.put(BigDecimal.class, StringConversors.BIG_DECIMAL);
        STRING_CONVERSORS = Collections.unmodifiableMap(mapConversors);
    }

    /**
     * Converte um tipo string em outro. O tipo destino deve ser primitivo
     * (boxing permitido), enum ou String.
     *
     * @param type   Tipo do campo.
     * @param string String a ser convertida.
     * @return Objeto com tipo compatível.
     * @throws UnsupportedOperationException Caso tipo não seja suportado.
     */
    public static <T> T stringToType(final Class<T> type,
                                     final String string) {

        final Object object;
        if (Conditionals.isEmpty(string)) {
            object = null;
        } else if (Enum.class.isAssignableFrom(type)) {
            object = Casts.valueOfEnum(type, string);
        } else {
            try {
                return getConversorFor(type).execute(string);
            } catch (Exception e) {
                throw new RuntimeException("Error converting string \"" + string + "\" to " + type.getName(), e);
            }
        }
        return (T) object;
    }

    public static <T> Action<String, T> getConversorFor(final Class<T> type) {
        if (STRING_CONVERSORS.containsKey(type)) {
            return Casts.simpleCast(STRING_CONVERSORS.get(type));
        } else {
            throw new UnsupportedOperationException("String conversor for type " + type.getName() + " was not found.");
        }
    }


    public static class StringConversors {

        public static final Action<String, String> STRING = new DummyAction<String>();
        public static final Action<String, Boolean> BOOLEAN = new AbstractActionThrowErrors<String, Boolean>() {
            @Override
            public Boolean execute(final String string) throws Exception {
                return "true".equals(string);
            }
        };
        public static final Action<String, Character> CHARACTER = new AbstractActionThrowErrors<String, Character>() {
            @Override
            public Character execute(final String string) throws Exception {
                if (string.length() != 1) {
                    throw new IllegalArgumentException("Expected one char but given " + string.length());
                }
                return Character.valueOf(string.charAt(0));
            }
        };

        public static final Action<String, Byte> BYTE = new AbstractActionThrowErrors<String, Byte>() {
            @Override
            public Byte execute(final String string) throws Exception {
                return Byte.valueOf(string);
            }
        };
        public static final Action<String, Short> SHORT = new AbstractActionThrowErrors<String, Short>() {
            @Override
            public Short execute(final String string) throws Exception {
                return Short.valueOf(string);
            }
        };
        public static final Action<String, Integer> INTEGER = new AbstractActionThrowErrors<String, Integer>() {
            @Override
            public Integer execute(final String string) throws Exception {
                return Integer.valueOf(string);
            }
        };
        public static final Action<String, Long> LONG = new AbstractActionThrowErrors<String, Long>() {
            @Override
            public Long execute(final String string) throws Exception {
                return Long.valueOf(string);
            }
        };
        public static final Action<String, Float> FLOAT = new AbstractActionThrowErrors<String, Float>() {
            @Override
            public Float execute(final String string) throws Exception {
                return Float.valueOf(string);
            }
        };
        public static final Action<String, Double> DOUBLE = new AbstractActionThrowErrors<String, Double>() {
            @Override
            public Double execute(final String string) throws Exception {
                return Double.valueOf(string);
            }
        };
        public static final Action<String, BigDecimal> BIG_DECIMAL = new AbstractActionThrowErrors<String, BigDecimal>() {
            @Override
            public BigDecimal execute(final String string) throws Exception {
                return new BigDecimal(string);
            }
        };
    }

}
